1   package org.apache.lucene.util;
2   
3   import org.apache.lucene.util.ByteBlockPool.Allocator;
4   
5   /*
6    * Licensed to the Apache Software Foundation (ASF) under one or more
7    * contributor license agreements.  See the NOTICE file distributed with
8    * this work for additional information regarding copyright ownership.
9    * The ASF licenses this file to You under the Apache License, Version 2.0
10   * (the "License"); you may not use this file except in compliance with
11   * the License.  You may obtain a copy of the License at
12   *
13   *     http://www.apache.org/licenses/LICENSE-2.0
14   *
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   */
21  
22  /**
23   * A {@link ByteBlockPool.Allocator} implementation that recycles unused byte
24   * blocks in a buffer and reuses them in subsequent calls to
25   * {@link #getByteBlock()}.
26   * <p>
27   * Note: This class is not thread-safe
28   * </p>
29   * @lucene.internal
30   */
31  public final class RecyclingByteBlockAllocator extends ByteBlockPool.Allocator {
32    private byte[][] freeByteBlocks;
33    private final int maxBufferedBlocks;
34    private int freeBlocks = 0;
35    private final Counter bytesUsed;
36    public static final int DEFAULT_BUFFERED_BLOCKS = 64;
37  
38    /**
39     * Creates a new {@link RecyclingByteBlockAllocator}
40     * 
41     * @param blockSize
42     *          the block size in bytes
43     * @param maxBufferedBlocks
44     *          maximum number of buffered byte block
45     * @param bytesUsed
46     *          {@link Counter} reference counting internally allocated bytes
47     */
48    public RecyclingByteBlockAllocator(int blockSize, int maxBufferedBlocks,
49        Counter bytesUsed) {
50      super(blockSize);
51      freeByteBlocks = new byte[maxBufferedBlocks][];
52      this.maxBufferedBlocks = maxBufferedBlocks;
53      this.bytesUsed = bytesUsed;
54    }
55  
56    /**
57     * Creates a new {@link RecyclingByteBlockAllocator}.
58     * 
59     * @param blockSize
60     *          the block size in bytes
61     * @param maxBufferedBlocks
62     *          maximum number of buffered byte block
63     */
64    public RecyclingByteBlockAllocator(int blockSize, int maxBufferedBlocks) {
65      this(blockSize, maxBufferedBlocks, Counter.newCounter(false));
66    }
67  
68    /**
69     * Creates a new {@link RecyclingByteBlockAllocator} with a block size of
70     * {@link ByteBlockPool#BYTE_BLOCK_SIZE}, upper buffered docs limit of
71     * {@link #DEFAULT_BUFFERED_BLOCKS} ({@value #DEFAULT_BUFFERED_BLOCKS}).
72     * 
73     */
74    public RecyclingByteBlockAllocator() {
75      this(ByteBlockPool.BYTE_BLOCK_SIZE, 64, Counter.newCounter(false));
76    }
77  
78    @Override
79    public byte[] getByteBlock() {
80      if (freeBlocks == 0) {
81        bytesUsed.addAndGet(blockSize);
82        return new byte[blockSize];
83      }
84      final byte[] b = freeByteBlocks[--freeBlocks];
85      freeByteBlocks[freeBlocks] = null;
86      return b;
87    }
88  
89    @Override
90    public void recycleByteBlocks(byte[][] blocks, int start, int end) {
91      final int numBlocks = Math.min(maxBufferedBlocks - freeBlocks, end - start);
92      final int size = freeBlocks + numBlocks;
93      if (size >= freeByteBlocks.length) {
94        final byte[][] newBlocks = new byte[ArrayUtil.oversize(size,
95            RamUsageEstimator.NUM_BYTES_OBJECT_REF)][];
96        System.arraycopy(freeByteBlocks, 0, newBlocks, 0, freeBlocks);
97        freeByteBlocks = newBlocks;
98      }
99      final int stop = start + numBlocks;
100     for (int i = start; i < stop; i++) {
101       freeByteBlocks[freeBlocks++] = blocks[i];
102       blocks[i] = null;
103     }
104     for (int i = stop; i < end; i++) {
105       blocks[i] = null;
106     }
107     bytesUsed.addAndGet(-(end - stop) * blockSize);
108     assert bytesUsed.get() >= 0;
109   }
110 
111   /**
112    * @return the number of currently buffered blocks
113    */
114   public int numBufferedBlocks() {
115     return freeBlocks;
116   }
117 
118   /**
119    * @return the number of bytes currently allocated by this {@link Allocator}
120    */
121   public long bytesUsed() {
122     return bytesUsed.get();
123   }
124 
125   /**
126    * @return the maximum number of buffered byte blocks
127    */
128   public int maxBufferedBlocks() {
129     return maxBufferedBlocks;
130   }
131 
132   /**
133    * Removes the given number of byte blocks from the buffer if possible.
134    * 
135    * @param num
136    *          the number of byte blocks to remove
137    * @return the number of actually removed buffers
138    */
139   public int freeBlocks(int num) {
140     assert num >= 0 : "free blocks must be >= 0 but was: "+ num;
141     final int stop;
142     final int count;
143     if (num > freeBlocks) {
144       stop = 0;
145       count = freeBlocks;
146     } else {
147       stop = freeBlocks - num;
148       count = num;
149     }
150     while (freeBlocks > stop) {
151       freeByteBlocks[--freeBlocks] = null;
152     }
153     bytesUsed.addAndGet(-count*blockSize);
154     assert bytesUsed.get() >= 0;
155     return count;
156   }
157 }